Skip to content

[Cocoa] Use NSBezelStyleFlexiblePush for buttons with custom font size#3210

Draft
HeikoKlare wants to merge 1 commit intoeclipse-platform:masterfrom
HeikoKlare:fix/cocoa-button-flexible-push-bezel-for-custom-font
Draft

[Cocoa] Use NSBezelStyleFlexiblePush for buttons with custom font size#3210
HeikoKlare wants to merge 1 commit intoeclipse-platform:masterfrom
HeikoKlare:fix/cocoa-button-flexible-push-bezel-for-custom-font

Conversation

@HeikoKlare
Copy link
Copy Markdown
Contributor

🤖 This PR was created with Claude Code.

Issue

When a custom font with a size larger than the macOS default of 13 pt is set on a push or toggle button, the button text is visually clipped. macOS internally assumes the default font size when drawing a button with NSRoundedBezelStyle and does not expand the rendered pill shape to accommodate taller text. This is reported in issue #3085.

Root cause

NSRoundedBezelStyle (value 1) is the standard rounded push button bezel on macOS. Its rendered height is fixed and optimised for the 13 pt system font. When SWT sets a larger custom font on an NSButton that still uses NSRoundedBezelStyle, the native rendering engine clips the text to the fixed bezel height.

Solution

Apple explicitly provides NSBezelStyleFlexiblePush for exactly this situation. According to the documentation, this style allows the button to adapt its height to the content, making it the correct choice whenever text larger than the default font size is displayed.

The constant is defined in NSButtonCell.h of the macOS 15.4 SDK as:

NSBezelStyleFlexiblePush = 2,
NSBezelStyleRegularSquare
    API_DEPRECATED_WITH_REPLACEMENT("NSBezelStyleFlexiblePush", macos(10.0, API_TO_BE_DEPRECATED))
    = NSBezelStyleFlexiblePush,

Note that NSBezelStyleFlexiblePush has the same integer value (2) as the long-deprecated NSRegularSquareBezelStyle constant that SWT already uses in setBounds() for oversized buttons. This PR introduces NSBezelStyleFlexiblePush as a properly-named constant in OS.java and uses it consistently.

The fix has two parts in Button.java:

  1. setFont() — when a custom font is set (Control.font != null), immediately switch the bezel style to NSBezelStyleFlexiblePush. When the custom font is cleared, restore NSRoundedBezelStyle. This ensures the correct style is active before any layout pass queries computeSize().

  2. setBounds() — extend the existing height-threshold condition with || font != null so that a subsequent layout pass never reverts the bezel style back to NSRoundedBezelStyle for buttons that carry a custom font.

Both guards apply only to PUSH and TOGGLE buttons without FLAT or WRAP, matching the scope of the existing bezel-style selection logic.

Test

A regression test test_computeSize_largerWithLargeCustomFont is added to Test_org_eclipse_swt_widgets_Button. It verifies that a push button reports a strictly greater preferred height after its font is changed to 50 pt, ensuring layout managers allocate sufficient space and the button text is not clipped.

Visual validation

The following snippet can be used to visually verify the fix. It shows a reference button with the default font alongside a button whose font size can be changed interactively via a combo box:

package org.eclipse.swt.snippets;

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class Snippet394 {

	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setText("Button Font Size Test (macOS issue #3085)");
		shell.setLayout(new GridLayout(2, false));

		FontData defaultFontData = display.getSystemFont().getFontData()[0];
		Font[] customFont = { null };

		new Label(shell, SWT.NONE).setText("Font Size:");
		Combo fontSizeCombo = new Combo(shell, SWT.READ_ONLY);
		fontSizeCombo.setItems("8", "10", "12", "13", "14", "16", "18", "20", "21", "22", "23", "24", "25", "26", "28", "30", "36", "48", "72");
		fontSizeCombo.select(5); // 16pt default
		fontSizeCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

		new Label(shell, SWT.NONE).setText("Default font:");
		Button defaultButton = new Button(shell, SWT.PUSH);
		defaultButton.setText("Default Font Button");

		new Label(shell, SWT.NONE).setText("Custom font:");
		Button customButton = new Button(shell, SWT.PUSH);
		customButton.setText("Custom Font Button");

		customFont[0] = new Font(display, defaultFontData.getName(), 16, defaultFontData.getStyle());
		customButton.setFont(customFont[0]);

		fontSizeCombo.addListener(SWT.Selection, e -> {
			int size = Integer.parseInt(fontSizeCombo.getText());
			Font old = customFont[0];
			customFont[0] = new Font(display, defaultFontData.getName(), size, defaultFontData.getStyle());
			customButton.setFont(customFont[0]);
			shell.layout(true, true);
			if (old != null) old.dispose();
		});

		shell.addListener(SWT.Dispose, e -> {
			if (customFont[0] != null) customFont[0].dispose();
		});

		shell.pack();
		shell.open();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) display.sleep();
		}
		display.dispose();
	}
}

Alternative proposal

PR #3086 by @xpomul addresses the same issue with a different approach: it overrides cellSizeForBounds() to manually add a fixed pixel offset (FONT_SIZE_HEIGHT_ADJUST = 8) to the cell height when a custom font larger than 16 pt is detected.

This proposal is preferable for the following reasons:

  • Uses the official Apple API. NSBezelStyleFlexiblePush is the mechanism Apple specifically designed and documents for buttons that need to accommodate non-default font sizes. Manual height arithmetic works around a symptom rather than using the intended solution.
  • No magic numbers. PR [Mac] Buttons with font sizes>23pt are not correctly rendered #3085 #3086 introduces two arbitrary constants: a font-size threshold of 16 pt and a height adjustment of 8 px. These values are not derived from any documented specification and may break with future macOS releases or display scaling factors.
  • No arbitrary font-size threshold. This fix activates for any explicitly set custom font, regardless of size, which is the correct semantic: if the developer chose to override the font, the button should adapt. PR [Mac] Buttons with font sizes>23pt are not correctly rendered #3085 #3086 silently does nothing for custom fonts between 13 pt and 16 pt.
  • Correct at layout time. By switching the bezel style in setFont(), computeSize() immediately returns the right preferred size. PR [Mac] Buttons with font sizes>23pt are not correctly rendered #3085 #3086 patches cellSizeForBounds(), which is only called in certain layout paths and may not be reached in all scenarios.
  • Leverages the native rendering engine. With NSBezelStyleFlexiblePush, macOS itself computes and renders the correct button dimensions. There is no risk of the manual offset drifting out of sync with what the native layer actually draws.

@HeikoKlare HeikoKlare marked this pull request as draft April 9, 2026 15:34
@HeikoKlare HeikoKlare force-pushed the fix/cocoa-button-flexible-push-bezel-for-custom-font branch from eaab3d8 to da567b9 Compare April 9, 2026 16:28
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Test Results

  170 files    170 suites   27m 12s ⏱️
4 680 tests 4 659 ✅  21 💤 0 ❌
6 598 runs  6 443 ✅ 155 💤 0 ❌

Results for commit 7e05b65.

♻️ This comment has been updated with latest results.

@HeikoKlare HeikoKlare force-pushed the fix/cocoa-button-flexible-push-bezel-for-custom-font branch from da567b9 to 53e410d Compare April 9, 2026 16:48
NSRoundedBezelStyle (the default rounded push button style on macOS) has
a
fixed height optimised for the system default font size of 13 pt. When a
larger custom font is set on a push or toggle button, the text overflows
the fixed rendering bounds and is visually clipped.

Apple provides NSBezelStyleFlexiblePush (previously known as the now-
deprecated NSRegularSquareBezelStyle) specifically for this situation:
it
allows the button cell to grow vertically and adapt its appearance to
whatever content height is required.

The fix has two parts:

1. setFont() – when the internal Cocoa font hook fires, check whether a
   custom font has been explicitly set on the widget (Control.font !=
null).
   If so, immediately switch the bezel style to NSBezelStyleFlexiblePush
so
   that a subsequent computeSize() / layout pass sees the correct style
and
   the cell reports the right preferred height. When the custom font is
   cleared (setFont(null)), the style is restored to
NSRoundedBezelStyle.

2. setBounds() – the existing height-threshold guard that switches bezel
   styles during layout is extended with "|| font != null" so that a
layout
   pass following a font change never inadvertently reverts the button
back
   to NSRoundedBezelStyle.

Both guards apply only to PUSH and TOGGLE buttons without the FLAT or
WRAP
style bits, mirroring the existing bezel-style selection logic.

The constant NSBezelStyleFlexiblePush is added to OS.java (value 2,
sourced
from NSButtonCell.h in the macOS 15.4 SDK) in alphabetical order
alongside
the other NSBezelStyle constants, making the intention explicit and
avoiding
reliance on the deprecated NSRegularSquareBezelStyle alias.

A regression test is added to Test_org_eclipse_swt_widgets_Button that
verifies a push button reports a greater preferred height after its font
size is increased to 50 pt. This ensures that layout managers allocate
sufficient space and the button text is not clipped.

Fixes eclipse-platform#3085
@HeikoKlare HeikoKlare force-pushed the fix/cocoa-button-flexible-push-bezel-for-custom-font branch from 53e410d to 7e05b65 Compare April 9, 2026 17:38
@tomaswolf
Copy link
Copy Markdown
Member

In macOS 14, the previously existing constant NSRegularSquareBezelStyle was renamed to NSBezelStyleFlexiblePush. The functionality is the same (as is the value).

Maybe this should be two commits: one simply renaming the constant in OS.java (and of course its uses), and then one adding the real fixes and the test: the || font != null in setBounds() and setting the bezel style when the font is changed in setFont().

@HeikoKlare
Copy link
Copy Markdown
Contributor Author

HeikoKlare commented Apr 9, 2026

In macOS 14, the previously existing constant NSRegularSquareBezelStyle was renamed to NSBezelStyleFlexiblePush. The functionality is the same (as is the value).

Yes, this applies to basically all NSBezelStyle constants. I planned to update them all at once. You are right that it probably makes sense to rename them before processing this PR.

Edit: Constant replacement is now done by this PR:

@xpomul
Copy link
Copy Markdown
Contributor

xpomul commented Apr 10, 2026

checked out and tested locally. It works fine and looks much better than my version.
Thanks! I hope this makes it into 2026-06!

@vogella
Copy link
Copy Markdown
Contributor

vogella commented Apr 10, 2026

Time for you @xpomul to get Claude AI or the foundation sponsered Copilot licence which also allow to use Claude (Opus) to also generate much better patches. ;-) As Eclipse committer can you request the free Copilot license in your profile. I highly recommned using the command line version of Copilot with Claude Opal as model (/model to trigger selection) or Claude directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants